<html lang="en">

<head>
    <meta charset="utf-8">
    <title>ncnn webassembly blazeface camera</title>
    <link rel="stylesheet" type="text/css" href="index.css?version=2.0.4">

    <style>
        a:link,a:visited{ 
            text-decoration:none;  
        } 
        a:hover{ 
            text-decoration:underline; 
        } 
        video {
            /* position: absolute;
            visibility: hidden; */
        }
        canvas {
            border: 1px solid lightgray;
        }
    </style>

</head>

<body>

    <div class="wrapper">
        <div class="remind_box">
            <h1>ncnn webassembly blazeface</h3>
        </div>
    </div>

    <div class='video_box'>
        <canvas id="canvas" width="640"></canvas>
        <video autoplay muted playsinline id="video"></video>
        <div class="clear"></div>
    </div>

    <script src="wasmFeatureDetect.js"></script>

    <script type='text/javascript'>
        var Module = {};

        var has_simd;
        var has_threads;

        var wasmModuleLoaded = false;
        var wasmModuleLoadedCallbacks = [];

        Module.onRuntimeInitialized = function() {
            wasmModuleLoaded = true;
            for (var i = 0; i < wasmModuleLoadedCallbacks.length; i++) {
                wasmModuleLoadedCallbacks[i]();
            }
        }

        wasmFeatureDetect.simd().then(simdSupported => {
            has_simd = simdSupported;

            wasmFeatureDetect.threads().then(threadsSupported => {
                has_threads = threadsSupported;

                if (has_simd & has_threads)
                {
                    blazeface_module_name = 'blazeface';
                }
                else
                {
                    blazeface_module_name = 'blazeface_ios';
                    console.log('cannot enable simd&threads.');
                }

                console.log('load ' + blazeface_module_name);

                var blazefacewasm = blazeface_module_name + '.wasm';
                var blazefacejs = blazeface_module_name + '.js';

                fetch(blazefacewasm)
                    .then(response => response.arrayBuffer())
                    .then(buffer => {
                        Module.wasmBinary = buffer;
                        var script = document.createElement('script');
                        script.src = blazefacejs;
                        script.onload = function() {
                            console.log('Emscripten boilerplate loaded.');
                        }
                        document.body.appendChild(script);
                    });

            });
        });

        var dst = null;
        var resultarray = null;
        var resultbuffer = null;
        window.addEventListener('DOMContentLoaded', function() {
            var isStreaming = false;
            video = document.getElementById('video');
            canvas = document.getElementById('canvas');
            ctx = canvas.getContext('2d');
            w = 640;
            h = 480;
            var constraints = { audio: false, video: { width: w, height: h} };
            navigator.mediaDevices.getUserMedia(constraints)
                .then(function(mediaStream) {
                    var video = document.querySelector('video');
                    video.srcObject = mediaStream;
                    video.onloadedmetadata = function(e) {
                        video.play();
                    };
                })
                .catch(function(err) {
                    console.log(err.message);
                });
            // Wait until the video stream canvas play
            video.addEventListener('canplay', function(e) {
                if (!isStreaming) {
                    // videoWidth isn't always set correctly in all browsers
                    if (video.videoWidth > 0) h = video.videoHeight / (video.videoWidth / w);
                    canvas.setAttribute('width', w);
                    canvas.setAttribute('height', h);
                    isStreaming = true;
                }
            }, false);

            // Wait for the video to start to play
            video.addEventListener('play', function() {
                //Setup image memory
                var id = ctx.getImageData(0, 0, canvas.width, canvas.height);
                var d = id.data;

                if (wasmModuleLoaded) {
                    mallocAndCallSFilter();
                } else {
                    wasmModuleLoadedCallbacks.push(mallocAndCallSFilter);
                }

                function mallocAndCallSFilter() {
                    dst = _malloc(d.length);

                    // max 20 objects
                    resultarray = new Float32Array(16 * 20);
                    resultbuffer = _malloc(16 * 20 * Float32Array.BYTES_PER_ELEMENT);

                    HEAPF32.set(resultarray, resultbuffer / Float32Array.BYTES_PER_ELEMENT);

                    //console.log("What " + d.length);

                    sFilter();
                }
            });

        });

        var class_names = [
            "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light",
            "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
            "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
            "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",
            "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
            "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
            "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone",
            "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
            "hair drier", "toothbrush"
        ];

        var colors = [
            "rgb( 54,  67, 244)",
            "rgb( 99,  30, 233)",
            "rgb(176,  39, 156)",
            "rgb(183,  58, 103)",
            "rgb(181,  81,  63)",
            "rgb(243, 150,  33)",
            "rgb(244, 169,   3)",
            "rgb(212, 188,   0)",
            "rgb(136, 150,   0)",
            "rgb( 80, 175,  76)",
            "rgb( 74, 195, 139)",
            "rgb( 57, 220, 205)",
            "rgb( 59, 235, 255)",
            "rgb(  7, 193, 255)",
            "rgb(  0, 152, 255)",
            "rgb( 34,  87, 255)",
            "rgb( 72,  85, 121)",
            "rgb(158, 158, 158)",
            "rgb(139, 125,  96)"
        ];

        function ncnn_blazeface() {
            var canvas = document.getElementById('canvas');
            var ctx = canvas.getContext('2d');

            var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            var data = imageData.data;

            HEAPU8.set(data, dst);

            var t0 = performance.now()
            _blazeface_ncnn(dst, canvas.width, canvas.height, resultbuffer);
            var t1 = performance.now()
            console.log("blazeface_ncnn " + (t1 - t0) + " milliseconds.")

            // resultarray
            var qaqarray = HEAPF32.subarray(resultbuffer / Float32Array.BYTES_PER_ELEMENT, resultbuffer / Float32Array.BYTES_PER_ELEMENT + 16 * 20);

            var i;
            for (i = 0; i < 20; i++) {
                var label = qaqarray[i * 16 + 0];
                var prob = qaqarray[i * 16 + 1];
                var bbox_x = qaqarray[i * 16 + 2];
                var bbox_y = qaqarray[i * 16 + 3];
                var bbox_w = qaqarray[i * 16 + 4];
                var bbox_h = qaqarray[i * 16 + 5];
                var p1_x = qaqarray[i * 16 + 6];
                var p1_y = qaqarray[i * 16 + 7];
                var p2_x = qaqarray[i * 16 + 8];
                var p2_y = qaqarray[i * 16 + 9];
                var p3_x = qaqarray[i * 16 + 10];
                var p3_y = qaqarray[i * 16 + 11];
                var p4_x = qaqarray[i * 16 + 12];
                var p4_y = qaqarray[i * 16 + 13];
                var p5_x = qaqarray[i * 16 + 14];
                var p5_y = qaqarray[i * 16 + 15];


                if (label == -233)
                    continue;

                console.log('qaq ' + label + ' = ' + prob);

                ctx.strokeStyle = colors[i % 19];
                ctx.strokeRect(bbox_x, bbox_y, bbox_w, bbox_h);

                ctx.fillStyle = "rgb(255,0,255)";
                ctx.fillRect(p1_x, p1_y, 5, 5);
                ctx.fillRect(p2_x, p2_y, 5, 5);
                ctx.fillRect(p3_x, p3_y, 5, 5);
                ctx.fillRect(p4_x, p4_y, 5, 5);
                ctx.fillRect(p5_x, p5_y, 5, 5);

                var text = class_names[label] + " = " + parseFloat(prob * 100).toFixed(2) + "%";

                ctx.textBaseline = 'top';
                var text_width = ctx.measureText(text).width;
                var text_height = parseInt(ctx.font, 10);

                var x = bbox_x;
                var y = bbox_y - text_height;
                if (y < 0)
                    y = 0;
                if (x + text_width > canvas.width)
                    x = canvas.width - text_width;

                ctx.fillStyle = "rgb(255,255,255)";
                ctx.fillRect(x, y, text_width, text_height);
                ctx.font = "20pt Arial";
                ctx.fillStyle = "rgb(0,0,0)";
                ctx.fillText(text, x, y);
            }
        }

        //Request Animation Frame function
        var sFilter = function() {
            if (video.paused || video.ended) return;

            ctx.fillRect(0, 0, w, h);
            ctx.drawImage(video, 0, 0, w, h);

            ncnn_blazeface();

            window.requestAnimationFrame(sFilter);
        }

    </script>

</body>

</html>
